'''
装饰器还能作为混入类的替代技术来实现
'''
class Descriptor:
    def __init__(self, name=None, **opts):
        self.name = name
        for key, value in opts.items():
            setattr(self, key, value)

    def __set__(self, instance, value):
        instance.__dict__[self.name] = value

# Decorator for applying type checking
def typed(expected_type, cls=None):
    if cls is None:
        return lambda cls: typed(expected_type, cls)
    super_set = cls.__set__

    def __set__(self, instance, value):
        if not isinstance(value, expected_type):
            raise TypeError('expected ' + str(expected_type))
        super_set(self, instance, value)

    cls.__set__ = __set__
    return cls


# Decorator for unsigned values
def unsigned(cls):
    super_set = cls.__set__

    def __set__(self, instance, value):
        if value < 0:
            raise ValueError('Expected >= 0')
        super_set(self, instance, value)

    cls.__set__ = __set__
    return cls


# Decorator for allowing sized values
def maxsized(cls):
    super_init = cls.__init__

    def __init__(self, name=None, **opts):
        if 'size' not in opts:
            raise TypeError('missing size option')
        super_init(self, name, **opts)

    cls.__init__ = __init__

    super_set = cls.__set__

    def __set__(self, instance, value):
        if len(value) >= self.size:
            raise ValueError('size must be < ' + str(self.size))
        super_set(self, instance, value)

    cls.__set__ = __set__
    return cls


# Specialized descriptors
@typed(int)
class Integer(Descriptor):
    pass


@unsigned
class UnsignedInteger(Integer):
    pass


@typed(float)
class Float(Descriptor):
    pass


@unsigned
class UnsignedFloat(Float):
    pass


@typed(str)
class String(Descriptor):
    pass


@maxsized
class SizedString(String):
    pass

def recipe1():
    class Stock():
        name = SizedString('name', size=8)
        shares = UnsignedInteger('shares')
        price = UnsignedFloat('price')

        def __init__(self, name, shares, price):
            self.name = name
            self.shares = shares
            self.price = price    

    s = Stock("name", 1, float(1))
    print(f"{vars(s) = }")

    try:
        s.shares = -1
    except ValueError as e:
        print(e)    
                 
def main():
    print('recipe1'.center(20, '*'))
    recipe1()


if __name__ == '__main__':
    main() 